package com.jzo2o.market.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.DbRuntimeException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.market.dto.request.CouponUseBackReqDTO;
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.DBException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.*;
import com.jzo2o.market.enums.ActivityStatusEnum;
import com.jzo2o.market.enums.CouponStatusEnum;
import com.jzo2o.market.mapper.CouponMapper;
import com.jzo2o.market.model.domain.Activity;
import com.jzo2o.market.model.domain.Coupon;
import com.jzo2o.market.model.domain.CouponUseBack;
import com.jzo2o.market.model.domain.CouponWriteOff;
import com.jzo2o.market.model.dto.request.CouponOperationPageQueryReqDTO;
import com.jzo2o.market.model.dto.request.SeizeCouponReqDTO;
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
import com.jzo2o.market.model.dto.response.CouponInfoResDTO;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponUseBackService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.market.utils.CouponUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.mysql.utils.PageUtils;
import com.jzo2o.redis.utils.RedisSyncQueueUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.jzo2o.common.constants.ErrorInfo.Code.SEIZE_COUPON_FAILD;
import static com.jzo2o.market.constants.RedisConstants.RedisKey.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-09-16
 */
@Service
@Slf4j
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Resource(name = "seizeCouponScript")
    private DefaultRedisScript<String> seizeCouponScript;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private IActivityService activityService;

    @Resource
    private ICouponUseBackService couponUseBackService;

    @Resource
    private ICouponWriteOffService couponWriteOffService;


    /**
     * 抢券
     *
     * @param seizeCouponReqDTO
     */
    @Override
    public void seizeCoupon(SeizeCouponReqDTO seizeCouponReqDTO) {
        //当前登录用户id
        long userId = UserContext.currentUserId();
        if (ObjectUtil.isNull(userId)) {
            throw new CommonException("请登录后再操作");
        }

        //校验活动的有效性
        //从redis查询活动信息
        ActivityInfoResDTO activityInfoResDTO = activityService.getActivityInfoByIdFromCache(seizeCouponReqDTO.getId());
        if (ObjectUtil.isNull(activityInfoResDTO)) {
            throw new CommonException(SEIZE_COUPON_FAILD, "活动不存在");
        }
        //如果活动未开始
        if (activityInfoResDTO.getDistributeStartTime().isAfter(LocalDateTime.now())) {
            throw new CommonException(SEIZE_COUPON_FAILD, "活动未开始");
        }
        //如果活动为空，或过期了
        if (activityInfoResDTO.getDistributeEndTime().isBefore(LocalDateTime.now())) {
            throw new CommonException(SEIZE_COUPON_FAILD, "活动已结束");
        }

        //准备调用 lua脚本的数据
        //活动id
        long activityId = seizeCouponReqDTO.getId();


        int n = 10; // 队列总数
        //准备调用lua脚本的keys和args

        //序号：就是redisKey后大括号中的内容，多个redisKey的内容必须一致，就是活动id % 10
        int index = (int) (activityId % n);

        //同步队列key  完整的key：QUEUE:COUPON:SEIZE:SYNC:{活动id%10}
        String couponSeizeSyncRedisKey = RedisSyncQueueUtils.getQueueRedisKey(COUPON_SEIZE_SYNC_QUEUE_NAME, index);
        //库存key
        String resourceStockRedisKey = String.format(COUPON_RESOURCE_STOCK, index);
        //抢券成功队列key
        String couponSeizeListRedisKey = String.format(COUPON_SEIZE_LIST, activityId, index);
        List<String> keys = Arrays.asList(couponSeizeSyncRedisKey, resourceStockRedisKey, couponSeizeListRedisKey);

        //调用抢券的lua脚本
        Object execute = redisTemplate.execute(seizeCouponScript, keys, activityId, userId);
        log.info("执行lua脚本的结果:{}", execute);
        if (ObjectUtils.isNull(execute)) {
            throw new CommonException(SEIZE_COUPON_FAILD, "抢券失败");
        }

        //lua返回的结果(转数字)
        long result = NumberUtils.parseLong(StrUtil.toString(execute));
        if (result > 0) {
            log.info("用户：{}抢券成功", userId);
            return;
        }
        if (result == -1) {
            throw new CommonException(SEIZE_COUPON_FAILD, "限领一张");
        }
        if (result == -2 || result == -4) {
            throw new CommonException(SEIZE_COUPON_FAILD, "已抢光");
        }
        throw new CommonException(SEIZE_COUPON_FAILD, "抢券失败");

    }

    /**
     * 获取可用的优惠券
     * @param totalAmount
     * @return
     */
    @Override
    public List<AvailableCouponsResDTO> getAvailable(BigDecimal totalAmount) {
        log.info("获取可用的优惠券，订单总金额：{}", totalAmount);
        Long userId = UserContext.currentUserId();
        if (ObjectUtil.isNull(userId)) {
            throw new CommonException("请登录后再操作");
        }

        // 查询优惠劵
        List<Coupon> couponList = this.lambdaQuery()
                // 属于当前用户的优惠券
                .eq(Coupon::getUserId, userId)
                // 优惠券还没有过期
                .gt(Coupon::getValidityTime, LocalDateTime.now())
                // 优惠券还没有使用
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())
                .list();
        if (CollectionUtil.isEmpty(couponList)) {
            return Collections.emptyList();
        }

        // 计算优惠金额 组装数据
        return couponList.stream()
                // 计算优惠金额
                .peek(coupon -> coupon.setDiscountAmount(CouponUtils.calDiscountAmount(coupon, totalAmount)))
                // 获取优惠金额大于0且小于订单金额的优惠券
                .filter(coupon -> coupon.getDiscountAmount().compareTo(BigDecimal.ZERO) > 0 && coupon.getDiscountAmount().compareTo(totalAmount) < 0)
                // 按优惠金额降序排序
                .sorted(Comparator.comparing(Coupon::getDiscountAmount).reversed())
                .map(coupon -> BeanUtil.copyProperties(coupon, AvailableCouponsResDTO.class))
                .collect(Collectors.toList());

    }

    /**
     * 核销优惠券
     * @param couponUseReqDTO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CouponUseResDTO use(CouponUseReqDTO couponUseReqDTO) {
        Long userId = UserContext.currentUserId();
        if (ObjectUtil.isNull(userId)) {
            throw new BadRequestException("请登录后再操作");
        }

        // 参数校验
        if (ObjectUtils.isNull(couponUseReqDTO.getOrdersId()) || ObjectUtils.isNull(couponUseReqDTO.getTotalAmount())) {
            throw new BadRequestException("优惠券核销的订单信息为空");
        }

        // 根据id查询优惠券
        Coupon coupon = this.getById(couponUseReqDTO.getId());
        // 判断优惠券是否存在
        if (ObjectUtil.isNull(coupon)) {
            // 不存在 直接结束
            throw new BadRequestException("优惠券不存在");
        }

        // 存在 计算优惠金额
        BigDecimal discountAmount = CouponUtils.calDiscountAmount(coupon, couponUseReqDTO.getTotalAmount());
        // 根据优惠券id标记优惠券表中该优惠券已使用、使用时间、订单id等
        boolean flag = this.lambdaUpdate()
                .set(Coupon::getDiscountAmount, discountAmount)
                .set(Coupon::getStatus, CouponStatusEnum.USED.getStatus())
                .set(Coupon::getUseTime, LocalDateTime.now())
                .set(Coupon::getOrdersId, couponUseReqDTO.getOrdersId())
                .set(Coupon::getUpdateTime, LocalDateTime.now())
                .eq(Coupon::getId, couponUseReqDTO.getId())
                .eq(Coupon::getUserId, userId)
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())
                .gt(Coupon::getValidityTime, LocalDateTime.now())
                .update();
        if (!flag) {
            throw new CommonException("优惠券核销失败");
        }

        log.info("优惠券核销成功>>>>>>");

        // 向优惠券核销表添加记录
        CouponWriteOff couponWriteOff = new CouponWriteOff();
        couponWriteOff.setCouponId(coupon.getId());
        couponWriteOff.setUserId(userId);
        couponWriteOff.setOrdersId(couponUseReqDTO.getOrdersId());
        couponWriteOff.setActivityId(coupon.getActivityId());
        couponWriteOff.setWriteOffTime(LocalDateTime.now());
        couponWriteOff.setWriteOffManName(coupon.getUserName());
        couponWriteOff.setWriteOffManPhone(coupon.getUserPhone());
        couponWriteOffService.save(couponWriteOff);

        // 返回优惠金额
        return new CouponUseResDTO(discountAmount);

    }

    /**
     * 优惠券退回
     *
     * @param couponUseBackReqDTO
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void useBack(CouponUseBackReqDTO couponUseBackReqDTO) {

        // 查询优惠券 判断是否存在
        Coupon coupon = this.getById(couponUseBackReqDTO.getId());
        if (ObjectUtil.isNull(coupon)) {
            // 不存在 直接结束
            throw new BadRequestException("优惠券不存在");
        }

        // 判断优惠劵是否已经过期
        boolean flag;
        if (coupon.getValidityTime().isAfter(LocalDateTime.now())) {
            // 还没过期
            // 更新优惠券，如果优惠券未过期，标记该优惠券未使用，清空订单id字段及使用时间字段。
            flag = this.lambdaUpdate()
                    .set(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())
                    .set(Coupon::getOrdersId, null)
                    .set(Coupon::getUseTime, null)
                    .set(Coupon::getUpdateTime, LocalDateTime.now())
                    .eq(Coupon::getId, couponUseBackReqDTO.getId())
                    .eq(Coupon::getStatus, CouponStatusEnum.USED.getStatus())
                    .gt(Coupon::getValidityTime, LocalDateTime.now())
                    .update();
        } else {
            // 已过期
            // 更新优惠券，如果优惠券已过期则标记该优惠券已作废
            flag = this.lambdaUpdate()
                    .set(Coupon::getStatus, CouponStatusEnum.VOIDED.getStatus())
                    .set(Coupon::getUpdateTime, LocalDateTime.now())
                    .eq(Coupon::getId, couponUseBackReqDTO.getId())
                    .eq(Coupon::getStatus, CouponStatusEnum.USED.getStatus())
                    .le(Coupon::getValidityTime, LocalDateTime.now())
                    .update();
        }
        if (!flag) {
            throw new CommonException("优惠券退回失败");
        }

        // 删除核销记录
        CouponWriteOff couponWriteOff = couponWriteOffService.lambdaQuery()
                .eq(CouponWriteOff::getCouponId, coupon.getId())
                .one();
        if (ObjectUtil.isNull(couponWriteOff)) {
            throw new CommonException("优惠券退回失败");
        }
        flag = couponUseBackService.removeById(couponWriteOff.getId());
        if (!flag) {
            throw new CommonException("优惠券退回失败");
        }


        // 添加优惠券退回记录
        CouponUseBack couponUseBack = new CouponUseBack();
        couponUseBack.setCouponId(coupon.getId());
        couponUseBack.setUserId(couponUseBackReqDTO.getUserId());
        couponUseBack.setUseBackTime(LocalDateTime.now());
        couponUseBack.setWriteOffTime(couponWriteOff.getWriteOffTime());
        flag = couponUseBackService.save(couponUseBack);
        if (!flag) {
            throw new CommonException("优惠券退回失败");
        }

        log.info("优惠券退回成功>>>>>");

    }


    @Override
    public PageResult<CouponInfoResDTO> queryForPageOfOperation(CouponOperationPageQueryReqDTO couponOperationPageQueryReqDTO) {
        // 1.数据校验
        if (ObjectUtils.isNull(couponOperationPageQueryReqDTO.getActivityId())) {
            throw new BadRequestException("请指定活动");
        }
        // 2.数据查询
        // 分页 排序
        Page<Coupon> couponQueryPage = PageUtils.parsePageQuery(couponOperationPageQueryReqDTO, Coupon.class);
        // 查询条件
        LambdaQueryWrapper<Coupon> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Coupon::getActivityId, couponOperationPageQueryReqDTO.getActivityId());
        // 查询数据
        Page<Coupon> couponPage = baseMapper.selectPage(couponQueryPage, lambdaQueryWrapper);

        // 3.数据转化，并返回
        return PageUtils.toPage(couponPage, CouponInfoResDTO.class);
    }

    @Override
    public List<CouponInfoResDTO> queryForList(Long lastId, Long userId, Integer status) {

        // 1.校验
        if (status > 3 || status < 1) {
            throw new BadRequestException("请求状态不存在");
        }
        // 2.查询准备
        LambdaQueryWrapper<Coupon> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 查询条件
        lambdaQueryWrapper.eq(Coupon::getStatus, status)
                .eq(Coupon::getUserId, userId)
                .lt(ObjectUtils.isNotNull(lastId), Coupon::getId, lastId);
        // 查询字段
        lambdaQueryWrapper.select(Coupon::getId);
        // 排序
        lambdaQueryWrapper.orderByDesc(Coupon::getId);
        // 查询条数限制
        lambdaQueryWrapper.last(" limit 10 ");
        // 3.查询数据(数据中只含id)
        List<Coupon> couponsOnlyId = baseMapper.selectList(lambdaQueryWrapper);
        //判空
        if (CollUtils.isEmpty(couponsOnlyId)) {
            return new ArrayList<>();
        }

        // 4.获取数据且数据转换
        // 优惠id列表
        List<Long> ids = couponsOnlyId.stream()
                .map(Coupon::getId)
                .collect(Collectors.toList());
        // 获取优惠券数据
        List<Coupon> coupons = baseMapper.selectBatchIds(ids);
        // 数据转换
        return BeanUtils.copyToList(coupons, CouponInfoResDTO.class);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void revoke(Long activityId) {
        lambdaUpdate()
                .set(Coupon::getStatus, CouponStatusEnum.VOIDED.getStatus())
                .eq(Coupon::getActivityId, activityId)
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())
                .update();
    }

    @Override
    public Integer countReceiveNumByActivityId(Long activityId) {
        return lambdaQuery().eq(Coupon::getActivityId, activityId)
                .count();
    }

    @Override
    public void processExpireCoupon() {
        lambdaUpdate()
                .set(Coupon::getStatus, CouponStatusEnum.INVALID.getStatus())
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())
                .le(Coupon::getValidityTime, DateUtils.now())
                .update();
    }
}
